package org.ouwh.fw.util.xml;

import java.util.ArrayList;
import java.util.Arrays;

public final class Node {
	private Node father = null;
	private ArrayList<Node> children = null;
	private String type = null;
	private Attribute[] attrs = null;
	protected String xmlText = null;
	private int lineNumber = 1;
	private String fileName = null;
	private String encoding = "GB2312";

	public Node() {
	}

	public Node(String type) {
		this.type = type;
	}

	public Node(String type, Attribute[] attrs) {
		this.type = type;
		this.attrs = attrs;
	}

	public final void setFather(Node father) {
		this.father = father;
	}

	public final void changeFather(Node father) {
		if (this.father != null)
			this.father.delChild(this);
		if (father != null)
			father.addChild(this);
		this.father = father;
	}

	public final void setXmlText(String text) {
		setXmlText(text, true);
	}

	public final void setXmlText(String text, boolean append) {
		if (append) {
			xmlText = (xmlText == null ? text : xmlText + text);
		} else {
			xmlText = text;
		}
	}

	public void setXmlValue(String value) {
		xmlText = value;
	}

	public final Node getFather() {
		return father;
	}

	public final void delChild(Node node) {
		if (children == null)
			return;
		children.remove(node);
	}

	public final int getChildrenNum() {
		return children == null ? 0 : children.size();
	}

	public final Node[] getChildren() {
		if (children == null)
			return new Node[0];
		return (Node[]) children.toArray(new Node[0]);
	}

	public final void delChildren() {
		if (children == null)
			return;
		children.clear();
	}

	public final Node[] getChildren(String type, Attribute[] atts) {
		Node[] allChild = getChildren();
		ArrayList<Node> rightNode = new ArrayList<>();
		for (int i = 0; i < allChild.length; i++) {
			if (!allChild[i].getXmlType().equals(type))
				continue;
			if (!Attribute.include(allChild[i].getXmlAttrs(), atts))
				continue;
			rightNode.add(allChild[i]);
		}
		return (Node[]) rightNode.toArray(new Node[0]);
	}

	public final Node[] getChildren2(String type, Attribute att) {
		Node[] allChild = getChildren();
		ArrayList<Node> rightNode = new ArrayList<>();
		for (int i = 0; i < allChild.length; i++) {
			if (!allChild[i].getXmlType().equals(type))
				continue;
			if (!Attribute.include2(allChild[i].getXmlAttrs(), att))
				continue;
			rightNode.add(allChild[i]);
		}
		return (Node[]) rightNode.toArray(new Node[0]);
	}

	public final Node[] getChildren3(Attribute att) {
		Node[] allChild = getChildren();
		ArrayList<Node> rightNode = new ArrayList<>();
		for (int i = 0; i < allChild.length; i++) {
			if (!Attribute.include2(allChild[i].getXmlAttrs(), att))
				continue;
			rightNode.add(allChild[i]);
		}
		return (Node[]) rightNode.toArray(new Node[0]);
	}

	public final void delChildren(String type, Attribute[] atts) {
		if (children == null)
			return;
		children.removeAll(Arrays.asList(getChildren(type, atts)));
	}

	public final void delChildren2(String type, Attribute att) {
		if (children == null)
			return;
		children.removeAll(Arrays.asList(getChildren2(type, att)));
	}

	public final Node getChild(String type, Attribute[] atts) {
		Node[] allChild = getChildren();
		for (int i = 0; i < allChild.length; i++) {
			if (!allChild[i].getXmlType().equals(type))
				continue;
			if (!Attribute.include(allChild[i].getXmlAttrs(), atts))
				continue;
			return allChild[i];
		}
		return null;
	}

	public final Node getChild2(String type, Attribute att) {
		Node[] allChild = getChildren();
		for (int i = 0; i < allChild.length; i++) {
			if (!allChild[i].getXmlType().equals(type))
				continue;
			if (!Attribute.include2(allChild[i].getXmlAttrs(), att))
				continue;
			return allChild[i];
		}
		return null;
	}

	public final void delChild(String type, Attribute[] atts) {
		delChild(getChild(type, atts));
	}

	public final void delChild2(String type, Attribute att) {
		delChild(getChild2(type, att));
	}

	public final Node getChild(String type, Attribute[] atts, int number) {
		if (number < 1)
			return null;
		Node[] allChild = getChildren();
		for (int i = 0, j = 0; i < allChild.length; i++) {
			if (!allChild[i].getXmlType().equals(type))
				continue;
			if (!Attribute.include(allChild[i].getXmlAttrs(), atts))
				continue;
			if (++j == number)
				return allChild[i];
		}
		return null;
	}

	public final void delChild(String type, Attribute[] atts, int number) {
		if (children == null)
			return;
		children.remove(getChild(type, atts, number));
	}

	public final Node getChild(String type, int number) {
		if (number < 1)
			return null;
		Node[] allChild = getChildren();
		if (number > allChild.length)
			return null;
		for (int i = 0, j = 0; i < allChild.length; i++) {
			if (allChild[i].getXmlType().equals(type))
				j++;
			if (j == number)
				return allChild[i];
		}
		return null;
	}

	public final void delChild(String type, int number) {
		delChild(getChild(type, number));
	}

	public final Node getChild(int number) {
		if (children == null)
			return null;
		if (number < 1 || number > children.size())
			return null;
		return (Node) children.get(number - 1);
	}

	public final void delChild(int number) {
		delChild(getChild(number));
	}

	public String getXmlType() {
		return type;
	}

	public Attribute[] getXmlAttrs() {
		return attrs;
	}

	public Object getXmlAttr(String name) {
		if (attrs == null)
			return null;
		for (int i = 0; i < attrs.length; i++)
			if (attrs[i].getName().equals(name))
				return attrs[i].getValue();
		return null;
	}

	public void setXmlAttr(String name, String value) {
		if (attrs == null)
			return;
		for (int i = 0; i < attrs.length; i++) {
			if (attrs[i].getName().equals(name)) {
				attrs[i].setValue(value);
				return;
			}
		}
	}

	public void addXmlAttr(String name, String value) {
		Attribute attr = new Attribute(name, value);
		addXmlAttr(attr);
	}

	public void addXmlAttr(Attribute attr) {
		Attribute[] newAttrs = attrs == null ? new Attribute[1]
				: new Attribute[attrs.length + 1];
		String attrName = attr.getName();
		for (int i = 0; i < newAttrs.length - 1; i++) {
			if (attrs[i].getName().equals(attrName))
				return;
			newAttrs[i] = attrs[i];
		}
		newAttrs[newAttrs.length - 1] = attr;
		attrs = newAttrs;
	}

	public void delXmlAttr(String name) {
		if (attrs == null)
			return;
		Attribute[] newAttrs = new Attribute[attrs.length - 1];
		for (int i = 0, j = 0; i < attrs.length; i++)
			if (!attrs[i].getName().equals(name)) {
				if (j >= newAttrs.length)
					return;
				newAttrs[j] = attrs[i];
				j++;
			}
		if (newAttrs.length == 0)
			attrs = null;
		else
			attrs = newAttrs;
	}

	public String getXmlText() {
		return xmlText;
	}

	public String getXmlText2() {
		return xmlText;
	}

	public final Node addChild(Node node) {
		if (children == null)
			children = new ArrayList<Node>();
		children.add(node);
		node.setFather(this);
		return node;
	}

	public final int getDepth() {
		Node node = this;
		for (int i = 0;; i++)
			if ((node = node.getFather()) == null)
				return i;
	}

	public final Node getRoot() {
		Node node = this;
		while (node.getFather() != null)
			node = node.getFather();
		return node;
	}

	private final static String xmlSign1 = "\t";

	protected final String tabString() {
		StringBuffer result = new StringBuffer();
		Node node = this;
		while ((node = node.getFather()) != null) {
			result.append(xmlSign1);
		}
		return result.toString();
	}

	private final static String xmlSign2 = "<";
	private final static String xmlSign3 = " ";
	private final static String xmlSign4 = "=\"";
	private final static String xmlSign5 = "\"";
	private final static String xmlSign6 = "/>\n";
	private final static String xmlSign7 = ">";
	// private final static String xmlSign8 = "\n";
	private final static String xmlSign9 = "</";
	private final static String xmlSignA = ">\n";

	private static final char IXC1 = '<';
	private static final char IXC2 = '\n';
	private static final char IXC3 = '&';
	private static final char IXC4 = '\0';

	private boolean isIllegalString(String str) {
		return (str.indexOf(IXC1) >= 0 || str.indexOf(IXC2) >= 0
				|| str.indexOf(IXC3) >= 0 || str.indexOf(IXC4) >= 0);
	}

	private static final String CDATA_HEAD = "<![CDATA[";
	private static final String CDATA_TAIL = "]]>";

	public final String toXml() {
		String tabString = tabString();
		StringBuffer result = new StringBuffer();
		String trueType = getXmlType();
		result.append(tabString);
		result.append(xmlSign2);
		result.append(trueType);
		Attribute[] atts = getXmlAttrs();
		for (int i = 0; atts != null && i < atts.length; i++) {
			result.append(xmlSign3);
			result.append(atts[i].getName());
			result.append(xmlSign4);
			String value = atts[i].getValue();
			value = (value == null ? "" : value);
			// result.append(value.replaceAll("&", "&amp;"));
			result.append(XMLParser.replaceAllAmpForXML(value));
			result.append(xmlSign5);
		}
		String text2 = getXmlText2();
		Node[] allChild = getChildren();
		int childNum = allChild.length;
		if (text2 == null && childNum == 0) {
			result.append(xmlSign6);
			return result.toString();
		}
		if (text2 != null) {
			if (isIllegalString(text2)) {
				result.append(xmlSignA);
				result.append(tabString);
				result.append('\t');
				result.append(CDATA_HEAD);
				// text2 = text2.replaceAll("\0", "*");
				result.append(text2);
				result.append(CDATA_TAIL);
				result.append('\n');
			} else {
				result.append(xmlSign7);
				result.append(text2);
			}
		} else {
			result.append(xmlSignA);
		}
		for (int i = 0; i < childNum; i++)
			result.append(allChild[i].toXml());
		if (text2 == null) {
			result.append(tabString);
		}
		result.append(xmlSign9);
		result.append(trueType);
		result.append(xmlSignA);
		return result.toString();
	}

	public String toString() {
		return toXml();
	}

	public String toDocument() {
		return "<?xml version=\"1.0\" encoding=\"" + encoding + "\"?>\n"
				+ getRoot().toXml();
	}

	public int getLineNumber() {
		return lineNumber;
	}

	public void setLineNumber(int i) {
		lineNumber = i;
	}

	public String getFileName() {
		return fileName;
	}

	public void setFileName(String fileName) {
		this.fileName = fileName;
	}

	public String getEncoding() {
		return getRoot().encoding;
	}

	public void setEncoding(String encoding) {
		getRoot().encoding = encoding;
	}

}
